package fictional.BL;

import java.io.*;
import java.util.*;
import java.net.MalformedURLException;
import java.net.URL;
import fictional.Common.*;

public class BL implements IBL {
	private IDAL dal;
	private User _user;
	private List<ShortCharacterItem> _allowed = new ArrayList<ShortCharacterItem>();
	private List<GenderItem> _allGenders;
	private List<SpeciesItem> _allSpecies;
	private List<OrganizationItem> _allOrganizations;
	private List<OccupationItem> _allOccupations;
	private List<PlaceItem> _allPlaces;
	private List<PowerItem> _allPowers;
	private static final String FREEBASE_RAW_PATH = "http://api.freebase.com/api/trans/raw";
	private static final String inputDelimiter = ",";
	private static final String authorizationError = "Action not authorized for this character";

	/**
	 * Class constructor
	 * @param dal An IDAL implementing class
	 */
	public BL(IDAL dal) throws IllegalArgumentException {
		if (dal == null) {
			throw new IllegalArgumentException("dal must have a value");
		}
		this.dal = dal;
	}

	//public methods
	
	@Override
	public List<GenderItem> getAllGenders(Response response) {
		response.setIs_Success(true);
		if (_allGenders == null) {
			_allGenders = dal.getAllGenders(response);
			if (!response.isIs_Success()) {
				_allGenders = null;
				return null;
			}
			Collections.sort(_allGenders);
		}
		return _allGenders;
	}

	@Override
	public List<PlaceItem> getAllPlaces(Response response) {
		response.setIs_Success(true);
		if (_allPlaces == null) {
			_allPlaces = dal.getAllPlaces(response);
			if (!response.isIs_Success()) {
				_allPlaces = null;
				return null;
			}
			Collections.sort(_allPlaces);
		}
		return _allPlaces;
	}

	@Override
	public List<SpeciesItem> getAllSpecies(Response response) {
		response.setIs_Success(true);
		if (_allSpecies == null) {
			_allSpecies = dal.getAllSpecies(response);
			if (!response.isIs_Success()) {
				_allSpecies = null;
				return null;
			}
			Collections.sort(_allSpecies);
		}
		return _allSpecies;
	}
	
	@Override
	public CharacterItem getCharacter(ShortCharacterItem character, Response response) {
		CharacterItem ci = dal.getCharacterById(character.getId(), response);
		if (!response.isIs_Success() || ci == null) {
			response.setIs_Success(false);
			return null;
		}
		if (ci.getArticle() != null) {
			loadArticle(ci);		
		}
		return ci;
	}
	
	@Override
	public ShortCharacterItem getShortCharacterById(int charId,
			Response response) {
		return dal.getShortCharacterById(charId, response);
	}
	
	@Override
	public ArrayList<ShortCharacterItem> getShortCharacterByName(String name,
			Response response) {	
		return dal.getShortCharacterByName(name, response);
	}

	@Override
	public CharacterItem getDefaultCharacter(Response response) {
		CharacterItem ret = dal.getDefaultCharacter(_user.getId() , response);
		if (!response.isIs_Success() || ret == null) {
			response.setIs_Success(false);
			return null;
		}
		if (ret.getArticle() != null) {
			loadArticle(ret);		
		}
		return ret;
	}

	@Override
	public void editCharacterStatus(CharacterItem character, String status,
			Response response) {
		dal.editStatus(status, character.getId(), response);
		if (response.isIs_Success()) {
			character.setStatus(status);			
		} 
	}

	@Override
	public boolean login(User user, Response response) {
		int id = dal.login(user.getUsername(), user.getPassword(), response);
		if (id == IDAL.OPERATION_FAILED || !response.isIs_Success()) {
			response.setIs_Success(false);
			if (response.getMessage() == null)
				response.setMessage("Login error");
			return false;
		}
		user.setId(id);
		_user = new User(user.getUsername(), user.getPassword());
		_user.setId(id);
		_allowed.clear();
		return true;
	}

	@Override
	public boolean deleteFriend(CharacterItem character,
			ShortCharacterItem friend, Response response) {
		if (character.getFriends().contains(friend) && isAllowedEditing(character)) {
			dal.deleteFriend(character.getId(), friend.getId(), response);
			if (response.isIs_Success()) {
				character.getFriends().remove(friend);
				return true;
			} else if (response.getMessage() == null) {
				response.setMessage("Error deleting friend");
			}
		} else {
			response.setIs_Success(false);
			response.setMessage("You don't have such friend");
		}
		return false;
	}
	
	@Override
	public void deleteWallPost(Wall wall, WallItem wallPost, Response response) {
		if (wall.getWallPosts().contains(wallPost)) { 
			if (isAllowedEditing(wall.getCharacter()) || isAllowedEditing(wallPost.getPoster())) {
				dal.deleteWallPost(wallPost, response);		
				if (response.isIs_Success()) 
					wall.removeWallPost(wallPost);
			} else {
				response.setIs_Success(false);
				response.setMessage(authorizationError);
			}
		} else {
			response.setIs_Success(false);
			response.setMessage("No such message in this wall");
		}
	}

	@Override
	public Wall getWall(CharacterItem character, Response response) {
		return dal.getWall(character, response);
	}

	@Override
	public void postToWall(CharacterItem poster, ShortCharacterItem postedTo,
			String text, Wall wall, Response response) {
		WallItem wallPost = new WallItem(-1, new Date(System.currentTimeMillis()), poster, postedTo, text);
		wallPost.setId(dal.postToWall(wallPost, response));
		if (response.isIs_Success()) {
			wall.addWallPost(wallPost);
		}
	}

	@Override
	public void deleteMessage(Messages messages, MessageItem message,
			Response response) {
		if (messages.getAllMessages().contains(message) && isAllowedEditing(messages.getCharacter())) {
			dal.deleteMessage(message.getId(), response);
			if (response.isIs_Success()) {
				messages.deleteMessage(message);
			}
		} else {
			response.setIs_Success(false);
			response.setMessage(authorizationError);
		}
	}

	@Override
	public Messages getMessages(CharacterItem character, boolean incomingMessages, Response response) {
		response.setIs_Success(false);
		if (isAllowedEditing(character))
			return dal.getMessages(character, incomingMessages, response);
		else 
			response.setMessage(authorizationError);		
		return null;
	}

	@Override
	public void sendMessage(CharacterItem sender, ShortCharacterItem sentTo,
			String message, Response response) {
		response.setIs_Success(false);
		if (sender.getId() == sentTo.getId()) {
			response.setMessage("You can't send a message to yourself");
			return;
		}
		MessageItem messageItem = new MessageItem(-1, sender, sentTo, new Date(System.currentTimeMillis()), message, false);
		messageItem.setId(dal.sendMessage(messageItem, response));
	}

	@Override
	public void createNewFriendRequest(CharacterItem from, ShortCharacterItem to,
			Response response) {
		if (from.getId() == to.getId()) {
			response.setIs_Success(false);
			response.setMessage("You can't request a friendship from yourself");
		}
		for (ShortCharacterItem friend : from.getFriends()) {
			if (friend.getId() == to.getId()) {
				response.setIs_Success(false);
				response.setMessage("You two are friends!");
				return;
			}
		}
		FriendRequestItem request = new FriendRequestItem(-1, from, to, new Date(System.currentTimeMillis()));
		request.setId(dal.sendFriendRequest(request, response));		
	}

	@Override
	public FriendRequests getFriendRequests(CharacterItem character, boolean toCharacter, 
			Response response) {
		FriendRequests requests = null;
		if (isAllowedEditing(character))  {
			requests = dal.getFriendRequests(character, toCharacter, response);
			if (!response.isIs_Success())
				requests = null;
		}
		else {
			response.setIs_Success(false);
			response.setMessage(authorizationError);
		}
		return requests;

	}

	@Override
	public void acceptFriendRequest(FriendRequests requests,
			FriendRequestItem request, Response response) {
		if (isAllowedEditing(requests.getCharacter())) {
			dal.addFriendship(request.getReceiver().getId(), request.getSender().getId(), response);
			if (response.isIs_Success()) {
				deleteFriendRequest(requests, request, response);
				requests.getCharacter().getFriends().add(request.getSender());
			}
		} else {
			response.setIs_Success(false);
			response.setMessage(authorizationError);
		}
	}

	@Override
	public void declineFriendRequest(FriendRequests requests,
			FriendRequestItem request, Response response) {
		if (isAllowedEditing(requests.getCharacter())) {
			deleteFriendRequest(requests, request, response);
		} else {
			response.setIs_Success(false);
			response.setMessage(authorizationError);
		}
	}

	@Override
	public List<ShortCharacterItem> getSuggestedCharacters(ShortCharacterItem character,
			Response response) {
		ArrayList<ShortCharacterItem> similar = dal.getSimilarCharacters(character.getId(),response);
		ArrayList<ShortCharacterItem> relatives = dal.getUnfriendedRelatives(character.getId(),response);
		if (relatives == null) {
			relatives = new ArrayList<ShortCharacterItem>();
		}
		if (similar != null) {
			relatives.addAll(similar);
		}
		return relatives;
	}

	@Override
	public void editCharacterInformation(CharacterItem character, String name,
			GenderItem gender, SpeciesItem species, String organizations,
			PlaceItem birthplace, String occupations, String superPowers,
			Response response) {
		response.setIs_Success(false);
		if (!isAllowedEditing(character)) {
			response.setMessage(authorizationError);
			return;
		}
		CharacterItem dupChar = new CharacterItem(character);
		ArrayList<OrganizationItem> orgItems = new ArrayList<OrganizationItem>();			
		ArrayList<PowerItem> powerItems = new ArrayList<PowerItem>();
		ArrayList<OccupationItem> occItems = new ArrayList<OccupationItem>();
		Set<OrganizationItem> setOrg = new TreeSet<OrganizationItem>();
		Set<OccupationItem> setOcc = new TreeSet<OccupationItem>();
		Set<PowerItem> setPow = new TreeSet<PowerItem>();
		boolean toChange = false;
		if (gender != null && dupChar.getGender().getId() != gender.getId()) {
			dupChar.setGender(gender);
			toChange = true;
		}
		if (species != null && dupChar.getSpecies().getId() != species.getId()) {
			dupChar.setSpecies(species);
			toChange = true;
		}
		if (birthplace != null &&  dupChar.getPlace_of_birth().getId() != birthplace.getId()) {
			dupChar.setPlace_of_birth(birthplace);
			toChange = true;
		}
		if ( organizations != null && !organizations.isEmpty()) {
			String[] orgs = parseMultipleValuesString(organizations);
			for (int i = 0 ; i < orgs.length ; i++) {
				OrganizationItem theOrgItem = new OrganizationItem(-1, null, orgs[i]);
				if (!setOrg.contains(theOrgItem))
					setOrg.add(theOrgItem);
				fillDbInfo(theOrgItem, response);
				if (!response.isIs_Success()) {
					return;
				}
			}
			orgItems.addAll(setOrg);
			toChange = true;
		}
		if (superPowers != null && !superPowers.isEmpty()){
			String powers[] = parseMultipleValuesString(superPowers);
			for (int i = 0 ; i < powers.length ; i++) {
				PowerItem thePowItem = new PowerItem(-1, null, powers[i]); 
				if (!setPow.contains(thePowItem)) 
					setPow.add(thePowItem);				
				fillDbInfo(thePowItem, response);
				if (!response.isIs_Success()) {
					return;
				}
			}
			powerItems.addAll(setPow);
			toChange = true;
		}
		if (occupations != null && !occupations.isEmpty()) {
			String occs[] = parseMultipleValuesString(occupations);
			for (int i = 0 ; i < occs.length ; i++) {
				OccupationItem theOccItem = new OccupationItem(-1, null, occs[i]); 
				if (!setOcc.contains(theOccItem)) 
					setOcc.add(theOccItem);				
				fillDbInfo(theOccItem, response);
				if (!response.isIs_Success()) {
					return;
				}
			}
			occItems.addAll(setOcc);
			toChange = true;
		}
		boolean dataCorrupted = false;
		if (toChange) {
			dal.updateCharacterDetails(dupChar, response);
			if (!response.isIs_Success()) {
				dataCorrupted = true;
			}
			if (orgItems.size() > 0) {
				dal.updateOrganizations(character, orgItems, response);
				dupChar.setOrganizations(orgItems);
				if (!response.isIs_Success()) {
					dataCorrupted = true;
				}
			}
			if (powerItems.size() > 0) {
				dal.updatePowers(dupChar, powerItems, response);
				dupChar.setPowers(powerItems);
				if (!response.isIs_Success()) {
					dataCorrupted = true;
				}
			}
			if (occItems.size() > 0) {
				dal.updateOccupations(dupChar, occItems, response);
				dupChar.setOccupations(occItems);
				if (!response.isIs_Success()) {
					dataCorrupted = true;
				}
			}
		}
		else
			response.setMessage("Nothing to update!");
			//update the data already updated in DB, into this character
			character.copyData(dupChar);
		if (dataCorrupted){
			response.setIs_Success(false);
			response.setMessage("DB problem, not all data managed to update");
		}
	}
	
	@Override
	public boolean insertNewUser(User user, String name, GenderItem gender,
			SpeciesItem species, String organizations, PlaceItem birthplace,
			String occupations, String superPowers, Response response) {
		response.setIs_Success(true);
		int id = dal.insertUser(user, response);
		if (response.isIs_Success() == false) {
			return false;
		}
		user.setId(id);
		_user = user;
		CharacterItem newChar = new CharacterItem(-1,"");
		newChar.setName(name);
		int char_id = dal.insertCharacter(user.getId(), name, response);
		if (char_id == IDAL.OPERATION_FAILED || !response.isIs_Success()) {
			response.setIs_Success(false);
			response.setMessage("Creating character failed");
			return false;
		}
		newChar.setId(char_id);
		if (gender != null || species != null || birthplace != null || !organizations.isEmpty() || !occupations.isEmpty() || !superPowers.isEmpty())
			editCharacterInformation(newChar, name, gender, species, organizations, birthplace, occupations, superPowers, response);
		if (response.isIs_Success())
				return true;
		return false;
	}
	
	@Override
	public void insertCharacterImage(CharacterItem character, String imageLink,
			Response response) {		
		for (ImageItem image : character.getImages()) {
			if (image.getLink().equalsIgnoreCase(imageLink)) {
				response.setIs_Success(false);
				response.setMessage("You have already added this image");
				return;
			}
		}
		int id = dal.insertCharacterImage(character, imageLink, response);
		if (response.isIs_Success()) {
			ImageItem img = new ImageItem(id,"");
			img.setLink(imageLink);
			character.getImages().add(img);
		}
	}

	@Override
	public void insertPoke(PokeItem poke, Response response) {
		dal.insertPoke(poke, response);
	}

	@Override
	public Pokes getPokes(CharacterItem character, Response response) {
		return dal.getPokes(character, response);
	}

	@Override
	public void deleteCharacterImage(CharacterItem character, ImageItem img, Response response) {
		response.setIs_Success(true);
		dal.deleteCharacterImage(img, response);
		if (response.isIs_Success()) { 
			character.getImages().remove(img);
			//in case this image was profile image, set another as profile image (if available)
			if (character.getProfileImage() != null && img.getId() == character.getProfileImage().getId()) {
				if (character.getImages().size() != 0)
					setProfileImage(character.getImages().get(0), character, response);
			}
		}
	}
	
	@Override
	public void setProfileImage(ImageItem img,CharacterItem character,Response response) {
		dal.setProfileImage(img, character, response);
		if (response.isIs_Success()) {
			img.setIs_profile(true);
			character.setProfileImage(img);
		}			
	}

	//private functions
	
	private List<PowerItem> getAllPowers(Response response) {
		response.setIs_Success(true);
		if (_allPowers == null) {
			_allPowers = dal.getAllPowers(response);
			if (!response.isIs_Success()) {
				_allPowers = null;
			}
		}
		return _allPowers;
	}

	private List<OrganizationItem> getAllOrganizations(Response response) {
		response.setIs_Success(true);
		if (_allOrganizations == null) {
			_allOrganizations = dal.getAllOrganizations(response);
			if (!response.isIs_Success()) {
				_allOrganizations = null;
			}
		}
		return _allOrganizations;
	}

	private List<OccupationItem> getAllOccupations(Response response) {
		response.setIs_Success(true);
		if (_allOccupations == null) {
			_allOccupations = dal.getAllOccupations(response);
			if (!response.isIs_Success()) {
				_allOccupations = null;
			}
		}
		return _allOccupations;
	}
	
	/**
	 * Checks if the current user is allowed editing this character
	 * @param character The character
	 * @return True if the user is allowed editing the supplied character
	 */
	private boolean isAllowedEditing(ShortCharacterItem character) {
		boolean isAllowedEdit = false;
		if (_user != null && _user.getId() > -1 && character.getId() > -1) {
			if (!_allowed.contains(character)) {
				Response response = new Response();
				isAllowedEdit = dal.isAllowedEditingCharacter(character.getId(), _user.getId(), response);
				if (response.isIs_Success() && isAllowedEdit) {
					_allowed.add(character);
				}
			} else {
				isAllowedEdit = true;
			}
		}
		return isAllowedEdit;
	}
	
	/**
	 * Helper function to delete friend request
	 * @param requests The requests
	 * @param request The request to be deleted
	 * @param response The response
	 */
	private void deleteFriendRequest(FriendRequests requests,
			FriendRequestItem request, Response response) {
		response.setIs_Success(false);
		if (requests.getRequests().contains(request)) {
			dal.deleteFriendRequest(request, response);
			if (response.isIs_Success())
				requests.removeRequest(request);
		} else {
			response.setMessage("No such friend request");
		}
	}

	/**
	 * Parses a string (comma separated) into an array, trailing leading spaces
	 */
	private String[] parseMultipleValuesString(String input) {
		String[] ret = input.split(inputDelimiter);
		for (int i = 0 ; i < ret.length ; i++) {
			//trail heading spaces
			while (ret[i].startsWith(" ") && ret[i].length() > 1) {
				ret[i] = ret[i].substring(1);
			}
		}
		return ret;
	}
	
	/**
	 * Fills a DB item with relevant id and mid
	 * @param item The item
	 * @param response The response
	 */
	private void fillDbInfo(DBItem item, Response response) {
		if (item instanceof OrganizationItem) {
			if (_allOrganizations == null) {
				getAllOrganizations(response);
				if (!response.isIs_Success()) {
					return;
				}
			}
			OrganizationItem oi = (OrganizationItem)item;
			for (OrganizationItem ref : _allOrganizations) {
				if (ref.getName().equalsIgnoreCase(oi.getName())) {
					oi.setId(ref.getId());
					oi.setMid(ref.getMid());
					oi.setName(ref.getName());
					return;
				}
			}
			if (oi.getId() == -1) {
				OrganizationItem oi2 = dal.insertOrganization(oi.getName(), response);
				if (oi2 != null && response.isIs_Success()) {
					_allOrganizations.add(oi2);
					oi.setId(oi2.getId());
					oi.setMid(oi2.getMid());
				}
			}
		} else if (item instanceof OccupationItem) {
			if (_allOccupations == null) {
				getAllOccupations(response);
				if (!response.isIs_Success()) {
					return;
				}
			}
			OccupationItem oi = (OccupationItem)item;
			for (OccupationItem ref : _allOccupations) {
				if (ref.getOccupation().equalsIgnoreCase(oi.getOccupation())) {
					oi.setId(ref.getId());
					oi.setMid(ref.getMid());
					oi.setOccupation(ref.getOccupation());
					return;
				}
			}
			if (oi.getId() == -1) {
				OccupationItem oi2 = dal.insertOccupation(oi.getOccupation(), response);
				if (oi2 != null && response.isIs_Success()) {
					_allOccupations.add(oi2);
					oi.setId(oi2.getId());
					oi.setMid(oi2.getMid());
				}
			}
		} else if (item instanceof PowerItem) {
			if (_allPowers == null) {
				getAllPowers(response);
				if (!response.isIs_Success()) {
					return;
				}
			}
			PowerItem oi = (PowerItem)item;
			for (PowerItem ref : _allPowers) {
				if (ref.getPower().equalsIgnoreCase(oi.getPower())) {
					oi.setId(ref.getId());
					oi.setMid(ref.getMid());
					oi.setPower(ref.getPower());
					return;
				}
			}			
			if (oi.getId() == -1) {
				PowerItem oi2 = dal.insertPower(oi.getPower(), response);
				if (oi2 != null && response.isIs_Success()) {
					_allPowers.add(oi2);
					oi.setId(oi2.getId());
					oi.setMid(oi2.getMid());
				}
			}
		}
	}

	private void loadArticle(CharacterItem ci) {
		String text = getArticleText(ci.getArticle().getMid());
		text = text.replace("<p>", "");
		text = text.replace("</p>", "");
		ci.getArticle().setText(text);		
	}
	
	private String getArticleText(String article_mid) {
		URL u;
		InputStream is = null;
		DataInputStream dis;
		String s;
		String out = "";

		try {
			u = new URL(FREEBASE_RAW_PATH+article_mid);
			is = u.openStream();         // throws an IOException
			dis = new DataInputStream(new BufferedInputStream(is));

			while ((s = dis.readLine()) != null) {
				out += s + "\n";
			}	      
			return out;

		} catch (MalformedURLException mue) {

		} catch (IOException ioe) {

		} finally {
			try {
				is.close();
			} catch (IOException ioe) {	          
			}
		}

		return "";
	}
}
